﻿namespace CSVParser
{
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;

    public static class CSVParser
    {
        #region Methods

        public static Dictionary<int, List<string>> CSVParseFile(Stream csvFileStream)
        {
            Dictionary<int, List<string>> rvalue = new Dictionary<int, List<string>>();
            string strLineText;
            int rowNumber = 0;
            List<string> propertyNames = new List<string>();
            using (StreamReader objReader = new StreamReader(csvFileStream))
            {
                while ((strLineText = objReader.ReadLine()) != null)
                {
                    rvalue.Add(rowNumber, CSVParseLine(strLineText));
                    rowNumber++;
                }
            }

            return rvalue;
        }

        public static List<string> CSVParseFirstLine(Stream csvFileStream)
        {
            List<string> rvalue = null;
            if (csvFileStream != null)
            {
                rvalue = new List<string>();
                string strLineText;
                using (StreamReader objReader = new StreamReader(csvFileStream))
                {
                    while ((strLineText = objReader.ReadLine()) != null)
                    {
                        rvalue = CSVParseLine(strLineText);
                        break;
                    }
                }
            }

            return rvalue;
        }

        public static List<FieldMapping> GetFreshFieldMappings(List<string>csvColumns)
        {
            List<FieldMapping> rvalue = new List<FieldMapping>();
            int index = 0;
            foreach (string column in csvColumns)
            {
                FieldMapping emptyMap = new FieldMapping();
                emptyMap.CsvColumnName = column;
                emptyMap.Index = index;
                rvalue.Add(emptyMap);
                index++;
            }
            return rvalue;
        }

        //the order of csvColumns coincide with order of parsedData - the first record of parsedData may be the csvColumns
        public static Dictionary<int, List<MappedValue>> GetMappedValues(List<FieldMapping> mappings, Dictionary<int, List<string>> parsedData)
        {
            Dictionary<int, List<MappedValue>> rvalue = null;
            if (mappings != null && parsedData != null)
            {
                //Dictionary per all records int = rownumber, mapped values is all fields and values.
                rvalue = new Dictionary<int, List<MappedValue>>();
                Dictionary<int, List<string>> dataToMap = parsedData;
                List<string> firstRow = dataToMap[0];
                //check first three values - if they match then it is the column names
                if (firstRow[0].Equals(mappings[0].CsvColumnName) && firstRow[1].Equals(mappings[1].CsvColumnName) && firstRow[2].Equals(mappings[2].CsvColumnName))
                {
                    dataToMap.Remove(0);

                }
                foreach (var record in dataToMap)
                {
                    int rowNumber = record.Key;
                    if (rowNumber > -1)
                    {
                        //the order or the record values will be the same order as the mappings
                        List<string> recordValues = record.Value;
                        int fieldCount = recordValues.Count;
                        List<MappedValue> recordFieldValues = new List<MappedValue>();
                        for (int i = 0; i < fieldCount; i++)
                        {
                            string value = recordValues[i];
                            FieldMapping targetMapping = mappings[i];
                            recordFieldValues.Add(new MappedValue(targetMapping, value));
                        }
                        if (recordFieldValues.Any())
                        {
                            rvalue.Add(rowNumber, recordFieldValues);
                        }
                    }
                }
            }
            return rvalue;
        }

        private static List<string> CSVParseLine(string strInputString)
        {
            int intCounter = 0, intLenght;
            StringBuilder strElem = new StringBuilder();
            List<string> alParsedCsv = new List<string>();
            intLenght = strInputString.Length;
            strElem = strElem.Append("");
            int intCurrState = 0;
            int[][] aActionDecider = new int[9][];

            //Build the state array
            aActionDecider[0] = new int[4] { 2, 0, 1, 5 };
            aActionDecider[1] = new int[4] { 6, 0, 1, 5 };
            aActionDecider[2] = new int[4] { 4, 3, 3, 6 };
            aActionDecider[3] = new int[4] { 4, 3, 3, 6 };
            aActionDecider[4] = new int[4] { 2, 8, 6, 7 };
            aActionDecider[5] = new int[4] { 5, 5, 5, 5 };
            aActionDecider[6] = new int[4] { 6, 6, 6, 6 };
            aActionDecider[7] = new int[4] { 5, 5, 5, 5 };
            aActionDecider[8] = new int[4] { 0, 0, 0, 0 };

            for (intCounter = 0; intCounter < intLenght; intCounter++)
            {
                intCurrState = aActionDecider[intCurrState][GetInputID(strInputString[intCounter])];
                //take the necessary action depending upon the state returned
                PerformAction(ref intCurrState, strInputString[intCounter], ref strElem, ref alParsedCsv);
            }
            intCurrState = aActionDecider[intCurrState][3];
            PerformAction(ref intCurrState, '\0', ref strElem, ref alParsedCsv);
            return alParsedCsv;
        }

        private static int GetInputID(char chrInput)
        {
            if (chrInput == '"')
            {
                return 0;
            }
            else if (chrInput == ',')
            {
                return 1;
            }
            else
            {
                return 2;
            }
        }

        private static void PerformAction(ref int intCurrState, char chrInputChar, ref StringBuilder strElem, ref List<string> alParsedCsv)
        {
            string strTemp = null;
            switch (intCurrState)
            {
                case 0:
                    //Seperate out value to array list
                    strTemp = strElem.ToString();
                    alParsedCsv.Add(strTemp);
                    strElem = new StringBuilder();
                    break;
                case 1:
                case 3:
                case 4:
                    //accumulate the character
                    strElem.Append(chrInputChar);
                    break;
                case 5:
                    //End of line reached. Seperate out value to array list
                    strTemp = strElem.ToString();
                    alParsedCsv.Add(strTemp);
                    break;
                case 6:
                    //Erroneous input. Reject line.
                    alParsedCsv.Clear();
                    break;
                case 7:
                    //wipe ending " and Seperate out value to array list
                    strElem.Remove(strElem.Length - 1, 1);
                    strTemp = strElem.ToString();
                    alParsedCsv.Add(strTemp);
                    strElem = new StringBuilder();
                    intCurrState = 5;
                    break;
                case 8:
                    //wipe ending " and Seperate out value to array list
                    strElem.Remove(strElem.Length - 1, 1);
                    strTemp = strElem.ToString();
                    alParsedCsv.Add(strTemp);
                    strElem = new StringBuilder();
                    //goto state 0
                    intCurrState = 0;
                    break;
            }
        }

        #endregion Methods
    }
}